# GF_PY3_CLASS_Finance_Indicator.py
# Create By GF 2024-01-22 18:16

import os

# Get Specific System Environment Variables.
LIBRARY_PATH:str = os.environ.get("GF_LIBRARY_PATH")

import sys

# Add "Library Path" to The Working Path of Python.
sys.path.append(LIBRARY_PATH)

# ################################################################################
# Mathematics Functions Related to Version 3.8.0 of Python.

def Safe_Multiply(self, multiplicand:float, Multiplier:float) -> float:

    if ((multiplicand == None) or (Multiplier == None)):
        # 如果被乘数为 None 或者乘数为 None。
        # ..........................................
        return None
    else:
        Result:float = (multiplicand * Multiplier)
        # ..........................................
        return Result

def Safe_Divide(self, Divisor:float, Dividend:float) -> float:

    if ((Dividend == 0.0) or (Dividend == None)):
        # 如果分母为 0 或者分母为 None。
        # ..........................................
        return None
    else:
        Result:float = (Divisor / Dividend)
        # ..........................................
        return Result

def List_Average(self, Lst:list) -> float:

    Result = self.Safe_Divide(sum(Lst), len(Lst))
    # ..............................................
    return Result

# ################################################################################
# JSON Functions Related to Version 3.8.0 of Python.

import json

def Read_JSONFile_and_Remove_Comments(JSONFilePath:str):

    JSONFile = open(JSONFilePath, 'r', encoding="utf-8")
    JSONString = JSONFile.read()
    
    # Define Regular Expressions to Match C-Style Comments.
    # (定义正则表达式来匹配 C 风格的注释)
    Comment_Pattern_1 = re.compile(r"\/\*.*\*\/")
    Comment_Pattern_2 = re.compile(r"\/\/.*")

    # Replace Comments with Regular Expressions.
    # (使用正则表达式替换掉注释)
    New_JSON_String = re.sub(Comment_Pattern_1, str(''), JSONString)
    New_JSON_String = re.sub(Comment_Pattern_2, str(''), New_JSON_String)

    # Parse JSON String.
    # (解析 JSON 字符串)
    ParsedJSON = json.loads(New_JSON_String)
    # ..............................................
    return ParsedJSON

def JSONString_Extract_Element_By_Index(JSONString:str, Index:int):

    # Convert "JSON String" to "Python List" (将 JSON 字符串转为 Python 列表)
    List_Obj = json.loads(JSONString)

    Element = List_Obj[Index]
    # ..............................................
    return Element

def JSONString_Replace_Element_By_Index(JSONString:str, Index:int, New_Element):

    # Convert "JSON String" to "Python List" (将 JSON 字符串转为 Python 列表)
    List_Obj = json.loads(JSONString)

    List_Obj[Index] = New_Element

    # 例如: Python 列表 [1, 2, 3] 将输出 '[1, 2, 3]'
    # (注意这里的输出实际上是带有双引号的, 并且数字之间有空格, 符合 JSON 标准)
    New_JSON_String = json.dumps(List_Obj)
    # ..............................................
    return New_JSON_String

# ################################################################################

# Mapping 类(Class) - 序列操作(Sequence Operate)。
class Mapping_SeqOpr(object):

    def __init__(self):

        # 通常使用 Map 映射操作属于单向迭代, 因此只有 向后填充(Fill Backward) 操作, 而不需要 向前填充(Fill Forward) 操作。
        self.PREPARED_FILL_VALUE_for_FILL_BACKWARD:object = None
        self.APPEARED_FILL_TYPES_for_FILL_BACKWARD_AND_COUNT:list = [] # Appeared Fill Types (出现的填充类型)
        self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT:object = None

    # 向后填充(Fill Backward).
    def Fill_Backward(self, Index:int, Value:object) -> object:

        if (Index == 1):

            self.PREPARED_FILL_VALUE_for_FILL_BACKWARD = Value
            # ......................................
            Fill_Value = Value
            # ......................................
            return Fill_Value

        else:

            if (Value == None):
                Fill_Value = self.PREPARED_FILL_VALUE_for_FILL_BACKWARD
                # ..................................
                return Fill_Value
            else:
                self.PREPARED_FILL_VALUE_for_FILL_BACKWARD = Value
                # ..................................
                Fill_Value = Value
                # ..................................
                return Fill_Value
        # ==========================================
        # End of Function.

    # 向后填充并计数(Fill Backward and Count).
    def Fill_Backward_and_Count(self, Index:int, Value:object) -> object:
    
        # Returns => "[(Value), (Group), (Times)]" => "[1, 2, 3]"
        # Example => "[1, 2, 3]" => [值 1, 第 2 批, 出现第 3 次]
        # Example => "[0, 1, 1]" => [值 0, 第 1 批, 出现第 1 次]
        # ..........................................
        # There are a Total of 4 Situations When Filling:
        # 1. Prepared Fill Value is Empty, Input Value is Non-Empty (Initialize Prepare Fill Value).
        # 2. Prepared Fill Value is Non-Empty, Input Value is Empty (Filling).
        # 3. Prepared Fill Value is Non-Empty, Input Value is Non-Empty (Reset Prepare Fill Value).
        # 4. Prepared Fill Value is Empty, Input Value is Empty (Skip).

        if (Index == 1):

            self.APPEARED_FILL_TYPES_for_FILL_BACKWARD_AND_COUNT.clear()
            # ......................................
            self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT = None

        # Condition: Prepared Fill Value is Empty, Input Value is Non-Empty (Initialize Prepare Fill Value).
        if (self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT == None) and (Value != None):

            # Used to Count The Number of Occurrences.
            self.APPEARED_FILL_TYPES_for_FILL_BACKWARD_AND_COUNT.append(Value)
            # ......................................
            # First Filling, with a Frequency of 1 Occurrence.
            # First Filling, with a Frequency of 1 Filling.
            Fill_Value = ("[%s, 1, 1]" % str(Value))
            # ......................................
            self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT = Fill_Value
            # ......................................
            return Fill_Value

        # Condition: Prepared Fill Value is Non-Empty, Input Value is Empty (Filling).
        elif (self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT != None) and (Value == None):

            Fill_Value = \
            self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT
            # ......................................
            # Used to Count The Number of Fillings.
            Times = JSONString_Extract_Element_By_Index(Fill_Value, 2)
            Times = (Times + 1)
            # ......................................
            Fill_Value = \
            JSONString_Replace_Element_By_Index(Fill_Value, 2, Times)
            # ......................................
            self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT = Fill_Value
            # ......................................
            return Fill_Value

        # Condition: Prepared Fill Value is Non-Empty, Input Value is Non-Empty (Reset Prepare Fill Value).
        elif (self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT != None) and (Value != None):

            # Used to Count The Number of Occurrences.
            self.APPEARED_FILL_TYPES_for_FILL_BACKWARD_AND_COUNT.append(Value)
            # ......................................
            # Number of Occurrences.
            Occurr_Num = \
            self.APPEARED_FILL_TYPES_for_FILL_BACKWARD_AND_COUNT.count(Value)
            # ......................................
            Fill_Value = ("[%s, %s, 1]" % (str(Value), str(Occurr_Num)))
            # ......................................
            self.PREPARED_FILL_VALUE_for_FILL_BACKWARD_AND_COUNT = Fill_Value
            # ......................................
            return Fill_Value

        # Others: Prepared Fill Value is Empty, Input Value is Empty (Skip).
        else:

            return None
        # ==========================================
        # End of Function.

    # 向下填充(Fill Down).
    def Fill_Down(self, Index:int, Value:object) -> object:

        Fill_Value = self.Fill_Backward(Index=Index, Value=Value)
        # ==========================================
        return Fill_Value

    # 向下填充并计数(Fill Down and Count).
    def Fill_Down_and_Count(self, Index:int, Value:object) -> object:

        Fill_Value = self.Fill_Backward_and_Count(Index=Index, Value=Value)
        # ==========================================
        return Fill_Value

# ################################################################################

# Mapping 类(Class) - 统计指标(Statistics Indicator)。
class Mapping_StatInd(object):

    # 增长率 (Growth Rate) 说明:
    # ..............................................
    # 增长率 (Growth Rate) 也称增长速度, 表示一定时期内某一数据指标的增长量与基期数据的比值, 用百分数表示。
    # 由于对比的基期不同, 增长率可以分为:
    # * 同比增长率(Year-on-Year Growth Rate / YoY+%)
    # * 环比增长率(Chain Growth Rate)
    # * 定基增长率(Growth Rate of a Fixed Base)
    # * 序列增长率(Growth Rate of Sequence)
    # ..............................................
    # 同比增长率 = (本期的指标值 - 同期的指标值) / 同期的指标值 * 100%。比如, 2009年3月与2008年3月的同比增长率的基期为2008年3月份；
    # 环比增长率 = (本期的指标值 - 上期的指标值) / 上期的指标值 * 100%。比如, 2008年5月与2008年4月的环比增长率的基期为相邻期 (2008年4月), 一般会将若干年的环比增长率进行列表和分析比较。
    # 定基增长率 = (本期的指标值 - 定期的指标值) / 定期的指标值 * 100%, 适用于长期发展比较。比如, 将某一时期1970年, 1980年, 1990年和2000年的GNP数值与1949年进行比较, 所获得的4个比例, 称为定基增长率。
    # 序列增长率 = (当前序列位置指标值 - 上个序列位置指标值) / 上个序列位置指标值 * 100%。

    def __init__(self):

        self.VALUE_LIST_for_Growth_Rate_of_Sequence:list = []

    # 序列增长率(Growth Rate of Sequence).
    def Growth_Rate_of_Sequence(self, Index:int, Curr_Value:float) -> float:

        if (Index == 1):
            self.VALUE_LIST_for_Growth_Rate_of_Sequence.clear() # -> 首次执行函数先清空全局列表变量。
            # ......................................
            self.VALUE_LIST_for_Growth_Rate_of_Sequence.append(Curr_Value)
            # ......................................
            return None
        else:
            self.VALUE_LIST_for_Growth_Rate_of_Sequence.append(Curr_Value)
            # ......................................
            Idx = (Index - 1) # -> 由于行号索引是从 1 开始, 但 Python 列表索引是从 0 开始, 所以需要减去 1。
            # ......................................
            Prev_Value = self.VALUE_LIST_for_Growth_Rate_of_Sequence[Idx - 1]
            # ......................................
            Growth_Rate = Safe_Divide((Curr_Value - Prev_Value), Prev_Value) * 1.00
            # ......................................
            return Growth_Rate

    # 定基增长率(Growth Rate of Constant Base).
    def Growth_Rate_of_Constant_Base(self, Constant_Base:float, Curr_Value:float) -> float:

        Growth_Rate = Safe_Divide((Curr_Value - Constant_Base), Constant_Base) * 1.00
        # ..........................................
        return Growth_Rate

# ################################################################################

# Mapping 类(Class) - 金融指标(Finance Indicator)。
class Mapping_FinInd(object):

    def __init__(self):

        self.CHANGE_LIST_for_RSI:list = []

    # 相对强弱指标 (Relative Strength Index).
    def RSI(self, Index:int, Period:int, Change:float) -> float:

        # 相对强弱指标 RSI 是用以计测市场供需关系和买卖力道的方法及指标。
        #
        # 公式一:
        # RSI(N) = A ÷ ( A + B ) × 100
        # A = N 日内收盘价所有上涨额度之和
        # B = N 日内收盘价所有下跌额度之和(取正数, 即乘以(-1))
        #
        # 公式二:
        # RS(相对强度) = N日内收盘价所有上涨额度之和的平均值 ÷ N日内收盘价所有下跌额度之和的平均值(取绝对值)
        # RSI(相对强弱指标) = 100 - 100 ÷ ( 1 + RS )
        #
        # 这两个公式虽然有些不同, 但计算的结果一样。
        #
        # 股票 RSI 三条线分别为 RSI1, RSI2, RSI3。
        # RSI1 是白线, 一般指 6 日相对强弱指标;
        # RSI2 是黄线, 一般指 12 日相对强弱指标;
        # RSI3 是紫线, 一般指 24 日相对强弱指标.

        # ------------------------------------------
        if   (Index == 1):

            self.CHANGE_LIST_for_RSI.clear()
            # ......................................
            self.CHANGE_LIST_for_RSI.append(Change)
            # ......................................
            return None

        elif (1 < Index and Index < Period):

            self.CHANGE_LIST_for_RSI.append(Change)
            # ......................................
            return None

        else:

            # --------------------------------------
            self.CHANGE_LIST_for_RSI.append(Change)

            # --------------------------------------
            Idx = (Index - 1) # -> 由于行号索引是从 1 开始, 但 Python 列表索引是从 0 开始, 所以需要减去 1。

            # --------------------------------------
            # 提取周期内上涨之和(Change Up)和周期内下跌之和(Change Down)。
            Chg_Up_Sum:float = 0.0 # -> 周期内的上涨之和.
            Chg_Dn_Sum:float = 0.0 # -> 周期内的下跌之和.
            # ......................................
            Chg_Up_Sum = sum([self.CHANGE_LIST_for_RSI[i] for i in range((Idx + 1 - Period), (Idx + 1)) if self.CHANGE_LIST_for_RSI[i] >= 0.0])
            Chg_Dn_Sum = sum([self.CHANGE_LIST_for_RSI[i] * (-1) for i in range((Idx + 1 - Period), (Idx + 1)) if self.CHANGE_LIST_for_RSI[i] < 0.0])

            # --------------------------------------
            # 计算 RSI。
            # ......................................
            # 每天既没上涨也没下跌, 最近 N 天的所有的 Up Move 之和是 0, 最近 N 天的所有的 Down Move 之和是 0, RS 会是 0 除以 0。
            # 但实际并不处于每天都是上涨或每天都是下跌的情况, 所以行情属于中性, 这种特殊情况定义 RSI 为 50。
            if   (Chg_Up_Sum == 0.0) and (Chg_Dn_Sum == 0.0):

                return float(50.0)

            # 每天都是下跌, 这将导致没有 Up Move 的日期, 最近 N 天的所有的 Up Move 之和是 0, Down Move 会是某个正数, 0 除以某个正数是 0。
            # 所以这种特殊情况会定义 RSI 为 0。
            elif (Chg_Up_Sum == 0.0) and (Chg_Dn_Sum != 0.0):

                return float(0.0)

            # 每天都是上涨, 这将导致没有 Down Move 的日期, 最近 N 天的所有的 Down Move 之和是 0, RS 会是某个正数除以 0, 数学上这是非法的。
            # 所以这种特殊情况会定义 RSI 为 100。
            elif (Chg_Up_Sum != 0.0) and (Chg_Dn_Sum == 0.0):

                return float(100.0)

            else:

                RS:float = (Chg_Up_Sum / Period) / (Chg_Dn_Sum / Period)
                # ..................................
                RSI:float = (100 - 100 / (1 + RS))
                # ..................................
                return RSI
        # ==========================================
        # End of Function.

# EOF Signed by GF.
